#include "script/glue/LuaGlue.h"

#include <OgreQuaternion.h>
#include <OgreMatrix3.h>
#include "core/Value.h"

namespace LuaGlue
{
	int Quaternion::create(lua_State *L)
	{
		Ogre::Quaternion *q = NULL;
		double w,x,y,z;

		switch(lua_gettop(L))
			{
			case 0:
				w = 1; x = y = z = 0;
				break;
			case 1:
				if(!lua_istable(L, 1))
					{
						luaL_typerror(L, 1, "table");
						break;
					}
				lua_gettable_number_i(L, w, 1);
				lua_gettable_number_i(L, x, 2);
				lua_gettable_number_i(L, y, 3);
				lua_gettable_number_i(L, z, 4);
				break;
			case 4:
				w = luaL_checknumber(L, 1);
				x = luaL_checknumber(L, 2);
				y = luaL_checknumber(L, 3);
				z = luaL_checknumber(L, 4);
				break;
			default:
				luaL_error(L, "Incorrect number of arguments for function");
				return 0;
			}

		q = new Ogre::Quaternion(w,x,y,z);

		Wrapper<Quaternion>::Instance(L, (void*)q);

		return 1;
	}

	int Quaternion::tostring (lua_State *L)
	{
		std::stringstream ss;
		switch(lua_gettop(L))
			{
			case 0:
				lua_pushstring(L, className);
				break;
			case 1:
				ss << *unwrap<Ogre::Quaternion>(L, 1);
				lua_pushstring(L, ss.str().c_str());
				break;
			default:
				return 0;
				break;
			}
		return 1;
	}

	int Quaternion::get( lua_State *L )
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L,1);
		const char *key = luaL_checkstring(L, 2);

		if(Wrapper<Quaternion>::Method(L, key)) return 1;

		if (strcmp(key, "w") == 0)
			lua_pushnumber(L, q->w);
		else if (strcmp(key, "x") == 0)
			lua_pushnumber(L, q->x);
		else if (strcmp(key, "y") == 0)
			lua_pushnumber(L, q->y);
		else if (strcmp(key, "z") == 0)
			lua_pushnumber(L, q->z);

		return 1;
	}

	int Quaternion::set( lua_State *L )
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L,1);
		const char *key = luaL_checkstring(L, 2);

		if(lua_isnumber(L,3))
			{
				double val = luaL_checknumber (L, 3);

				if (strcmp(key, "w") == 0)
					q->w = val;
				else if (strcmp(key, "x") == 0)
					q->x = val;
				else if (strcmp(key, "y") == 0)
					q->y = val;
				else if (strcmp(key, "z") == 0)
					q->z = val;
			}

		return 1;
	}

	int Quaternion::add(lua_State *L)
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L, 1);
		Ogre::Quaternion* p = unwrap<Ogre::Quaternion>(L, 2);
		Ogre::Quaternion* r = new Ogre::Quaternion((*q) + (*p));
		Wrapper<Quaternion>::Instance(L, (void*)r);
		return 1;
	}

	int Quaternion::mul(lua_State *L)
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L, 1);

		switch(lua_type(L, 2))
			{
			case LUA_TNUMBER:
			{
				double r = luaL_checknumber(L, 2);
				Ogre::Quaternion* p = new Ogre::Quaternion((*q) * r);
				Wrapper<Quaternion>::Instance(L, (void*)p);
				return 1;
			}
			break;
			case LUA_TTABLE:
			{
				lua_getfield(L, 2, "__tostring");
				lua_call(L, 0, 1);
				const std::string& str = luaL_checkstring (L, -1);
				lua_pop(L,1);
				switch(TypeInfo::getType(str))
					{
					case VECTOR3:
					{
						Ogre::Vector3* v = unwrap<Ogre::Vector3>(L, 2);
						*v = (*q) * (*v);
						Wrapper<Vector3>::Instance(L, (void*)v);
					}
					break;
					case QUATERNION:
					{
						Ogre::Quaternion* p = unwrap<Ogre::Quaternion>(L, 2);
						Ogre::Quaternion* r = new Ogre::Quaternion((*q) * (*p));
						Wrapper<Quaternion>::Instance(L, (void*)r);
					}
					break;
					default:
						lua_pushnil(L);
						luaL_error(L, "Wrong type of argument for function");
						return 0;
						break;
					}
			}
			break;
			default:
				luaL_error(L, "Incorrect number of arguments for function, expected 1");
				break;
			}

		return 1;
	}

	int Quaternion::FromAngleAxis(lua_State *L)
	{
		lua_pop(L,1);
		Ogre::Quaternion* q = new Ogre::Quaternion();
		Wrapper<Quaternion>::Instance(L, (void*)q);
		return 1;
	}

	int Quaternion::Euler(lua_State *L)
	{
		Ogre::Quaternion* q;
		Ogre::Matrix3 m;
		double x,y,z;

		switch(lua_gettop(L))
			{
			case 3:
				q = new Ogre::Quaternion();
				x = luaL_checknumber(L, 1);
				y = luaL_checknumber(L, 2);
				z = luaL_checknumber(L, 3);
				break;
			case 4:
				q = unwrap<Ogre::Quaternion>(L, 1);
				x = luaL_checknumber(L, 2);
				y = luaL_checknumber(L, 3);
				z = luaL_checknumber(L, 4);
				break;
			default:
				luaL_error(L, "Incorrect number of arguments for function");
				return 0;
				break;
			}

		m.FromEulerAnglesXYZ(Ogre::Degree(x), Ogre::Degree(y), Ogre::Degree(z));
		q->FromRotationMatrix(m);

		Wrapper<Quaternion>::Instance(L, (void*)q);
		return 1;
	}

	int Quaternion::normalise(lua_State *L)
	{
		lua_pushnumber(L, unwrap<Ogre::Quaternion>(L, 1)->normalise());
		return 1;
	}

	int Quaternion::Slerp(lua_State *L)
	{
		Ogre::Quaternion* r = new Ogre::Quaternion();

		double f = luaL_checknumber(L, 1);
		Ogre::Quaternion* q1 = unwrap<Ogre::Quaternion>(L, 2);
		Ogre::Quaternion* q2 = unwrap<Ogre::Quaternion>(L, 3);

		*r = Ogre::Quaternion::Slerp(f, *q1, *q2);

		Wrapper<Quaternion>::Instance(L, (void*)r);
		return 1;
	}

	int Quaternion::xAxis(lua_State *L)
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L, 1);
		Ogre::Vector3* v = new Ogre::Vector3(q->xAxis());
		if(v->isNaN()) return 0;
		Wrapper<Vector3>::Instance(L, (void*)v);
		return 1;
	}

	int Quaternion::yAxis(lua_State *L)
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L, 1);
		Ogre::Vector3* v = new Ogre::Vector3(q->yAxis());
		if(v->isNaN()) return 0;
		Wrapper<Vector3>::Instance(L, (void*)v);
		return 1;
	}

	int Quaternion::zAxis(lua_State *L)
	{
		Ogre::Quaternion* q = unwrap<Ogre::Quaternion>(L, 1);
		Ogre::Vector3* v = new Ogre::Vector3(q->zAxis());
		if(v->isNaN()) return 0;
		Wrapper<Vector3>::Instance(L, (void*)v);
		return 1;
	}
}


DECLARE(Quaternion)
META(Quaternion)
{"__index",    LuaGlue::Quaternion::get},
{"__newindex", LuaGlue::Quaternion::set},
{"__tostring", LuaGlue::Quaternion::tostring},
//{"__add", 	   LuaGlue::Quaternion::add},
//{"__mul", 	   LuaGlue::Quaternion::mul},
METHODS(Quaternion)
method(LuaGlue::Quaternion, add),
method(LuaGlue::Quaternion, mul),
method(LuaGlue::Quaternion, tostring),
//method(LuaGlue::Quaternion, normalise),
method(LuaGlue::Quaternion, Slerp),
method(LuaGlue::Quaternion, Euler),
method(LuaGlue::Quaternion, xAxis),
method(LuaGlue::Quaternion, yAxis),
method(LuaGlue::Quaternion, zAxis),
END
